package demo.java.nio;

import org.junit.Test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
 * @(#)Ping.java	1.2 01/12/13
 * Connect to each of a list of hosts and measure the time required to complete
 * the connection.  This example uses a selector and two additional threads in
 * order to demonstrate non-blocking connects and the multithreaded use of a
 * selector.
 *
 */
public class PingDemo {

    // The default daytime port
    static int DAYTIME_PORT = 13;

    // The port we'll actually use
    static int port = DAYTIME_PORT;

    // Representation of a ping target
    static class Target {
        InetSocketAddress inetSocketAddress;
        SocketChannel socketChannel;
        Exception exception;
        long connectStart;
        long connectFinish = 0;
        boolean shown = false;

        Target(String host) {
            try {
                inetSocketAddress = new InetSocketAddress(InetAddress.getByName(host), port);
            } catch (IOException x) {
                exception = x;
            }
        }

        void show() {
            String result;
            if (connectFinish != 0) {
                result = Long.toString(connectFinish - connectStart) + "ms";
            } else if (exception != null) {
                result = exception.toString();
            } else {
                result = "Timed out";
            }
            System.out.println(inetSocketAddress + " : " + result);
            shown = true;
        }

    }

    // Thread for printing targets as they're heard from
    static class Printer extends Thread {
        LinkedList<Target> linkedList = new LinkedList<Target>();

        Printer() {
            setName("Printer");
            setDaemon(true);
        }

        void add(Target t) {
            synchronized (linkedList) {
                linkedList.add(t);
                linkedList.notify();
            }
        }

        public void run() {
            try {
                for (; ; ) {
                    Target target = null;
                    synchronized (linkedList) {
                        while (linkedList.size() == 0) {
                            linkedList.wait();
                        }
                        target = (Target) linkedList.removeFirst();
                    }
                    target.show();
                }
            } catch (InterruptedException x) {
                return;
            }
        }

    }

    // Thread for connecting to all targets in parallel via a single selector
    static class Connector extends Thread {
        Selector selector;
        Printer printer;

        // List of pending targets. We use this list because if we try to
        // register a channel with the selector while the connector thread is
        // blocked in the selector then we will block.
        //
        LinkedList<Target> linkedList = new LinkedList<Target>();

        Connector(Printer pr) throws IOException {
            printer = pr;
            selector = Selector.open();
            setName("Connector");
        }

        // Initiate a connection sequence to the given target and add the
        // target to the pending-target list
        //
        void add(Target target) {
            SocketChannel socketChanel = null;
            try {
                // Open the channel, set it to non-blocking, initiate connect
                socketChanel = SocketChannel.open();
                socketChanel.configureBlocking(false);

                boolean connected = socketChanel.connect(target.inetSocketAddress);

                // Record the time we started
                target.socketChannel = socketChanel;
                target.connectStart = System.currentTimeMillis();

                if (connected) {
                    target.connectFinish = target.connectStart;
                    socketChanel.close();
                    printer.add(target);
                } else {
                    // Add the new channel to the pending list
                    synchronized (linkedList) {
                        linkedList.add(target);
                    }

                    // Nudge the selector so that it will process the pending
                    // list
                    selector.wakeup();
                }
            } catch (IOException x) {
                if (socketChanel != null) {
                    try {
                        socketChanel.close();
                    } catch (IOException xx) {
                    }
                }
                target.exception = x;
                printer.add(target);
            }
        }

        // Process any targets in the pending list
        //
        void processPendingTargets() throws IOException {
            synchronized (linkedList) {
                while (linkedList.size() > 0) {
                    Target target = (Target) linkedList.removeFirst();
                    try {

                        // Register the channel with the selector, indicating
                        // interest in connection completion and attaching the
                        // target object so that we can get the target back
                        // after the key is added to the selector's
                        // selected-key set
                        target.socketChannel.register(selector, SelectionKey.OP_CONNECT, target);
                    } catch (IOException x) {

                        // Something went wrong, so close the channel and
                        // record the failure
                        target.socketChannel.close();
                        target.exception = x;
                        printer.add(target);
                    }
                }

            }
        }

        // Process keys that have become selected
        void processSelectedKeys() throws IOException {
            for (Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); iterator.hasNext(); ) {

                // Retrieve the next key and remove it from the set
                SelectionKey selectionKey = iterator.next();
                iterator.remove();

                // Retrieve the target and the channel
                Target target = (Target) selectionKey.attachment();
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                // Attempt to complete the connection sequence
                try {
                    if (socketChannel.finishConnect()) {
                        selectionKey.cancel();
                        target.connectFinish = System.currentTimeMillis();
                        socketChannel.close();
                        printer.add(target);
                    }
                } catch (IOException x) {
                    socketChannel.close();
                    target.exception = x;
                    printer.add(target);
                }
            }
        }

        volatile boolean shutdown = false;

        // Invoked by the main thread when it's time to shut down
        //
        void shutdown() {
            shutdown = true;
            selector.wakeup();
        }

        // Connector loop
        //
        public void run() {
            for (; ; ) {
                try {
                    int n = selector.select();
                    if (n > 0)
                        processSelectedKeys();
                    processPendingTargets();
                    if (shutdown) {
                        selector.close();
                        return;
                    }
                } catch (IOException x) {
                    x.printStackTrace();
                }
            }
        }
    }

    public static void test(String[] args) throws InterruptedException, IOException {
        if (args.length < 1) {
            System.err.println("Usage: java Ping [port] host...");
            return;
        }
        int firstArg = 0;

        // If the first argument is a string of digits then we take that
        // to be the port number to use
        if (Pattern.matches("[0-9]+", args[0])) {
            port = Integer.parseInt(args[0]);
            firstArg = 1;
        }

        // Create the threads and start them up
        Printer printer = new Printer();
        printer.start();
        Connector connector = new Connector(printer);
        connector.start();

        // Create the targets and add them to the connector
        LinkedList<Target> targets = new LinkedList<Target>();
        for (int i = firstArg; i < args.length; i++) {
            Target t = new Target(args[i]);
            targets.add(t);
            connector.add(t);
        }

        // Wait for everything to finish
        Thread.sleep(2000);
        connector.shutdown();
        connector.join();

        // Print status of targets that have not yet been shown
        for (Iterator<Target> i = targets.iterator(); i.hasNext(); ) {
            Target t = (Target) i.next();
            if (!t.shown)
                t.show();
        }
    }

    @Test
    public void testPing1() throws InterruptedException, IOException {
        String[] args = {"localhost", "127.0.0.1"};
        if (args.length < 1) {
            System.err.println("Usage: java Ping [port] host...");
            return;
        }
        int firstArg = 0;

        // If the first argument is a string of digits then we take that
        // to be the port number to use
        if (Pattern.matches("[0-9]+", args[0])) {
            port = Integer.parseInt(args[0]);
            firstArg = 1;
        }

        // Create the threads and start them up
        Printer printer = new Printer();
        printer.start();
        Connector connector = new Connector(printer);
        connector.start();

        // Create the targets and add them to the connector
        LinkedList<Target> targets = new LinkedList<Target>();
        for (int i = firstArg; i < args.length; i++) {
            Target t = new Target(args[i]);
            targets.add(t);
            connector.add(t);
        }

        // Wait for everything to finish
        Thread.sleep(2000);
        connector.shutdown();
        connector.join();

        // Print status of targets that have not yet been shown
        for (Iterator<Target> i = targets.iterator(); i.hasNext(); ) {
            Target t = (Target) i.next();
            if (!t.shown)
                t.show();
        }
    }


    /**
     * 1.Jdk1.5的InetAddresss方式。自从Java 1.5，java.net包中就实现了ICMP ping的功能。
     * 使用时应注意，如果远程服务器设置了防火墙或相关的配制，可能会影响到结果。
     * 另外，由于发送ICMP请求需要程序对系统有一定的权限，当这个权限无法满足时， isReachable方法将试着连接远程主机的TCP端口 7(Echo)。
     *
     * @param ipAddress
     * @return
     * @throws Exception
     */
    public static boolean ping(String ipAddress) throws IOException {
        int timeOut = 3000;  //超时应该在3钞以上
        boolean status = InetAddress.getByName(ipAddress).isReachable(timeOut);     // 当返回值是true时，说明host是可用的，false则不可。
        return status;
    }

    @Test
    public void testPing2() {
        String ipAddress = "127.0.0.1";
        try {
            boolean reach = ping(ipAddress);
            System.err.println("ping " + ipAddress + " return : " + reach);
        } catch (Exception e) {
            e.printStackTrace();
        }

        ipAddress = "192.168.82.163";
        try {
            boolean reach = ping(ipAddress);
            System.err.println("ping " + ipAddress + " return : " + reach);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 直接调用CMD
     *
     * @param ipAddress
     * @throws Exception
     */
    public static void ping02(String ipAddress) throws Exception {
        String line = null;
        try {
            Process pro = Runtime.getRuntime().exec("ping " + ipAddress);
            BufferedReader buf = new BufferedReader(new InputStreamReader(
                    pro.getInputStream()));
            while ((line = buf.readLine()) != null) {
                System.out.println(line);
            }
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    /**
     * Java调用控制台执行ping命令
     * <p>
     * 具体的思路是这样的：
     * <p>
     * 通过程序调用类似“ping 127.0.0.1 -n 10 -w 4”的命令，这命令会执行ping十次，如果通顺则会输出类似“来自127.0.0.1的回复： 字节=32 时间<1ms TTL=64”的文本（具体数字根据实际情况会有变化），其中中文是根据环境本地化的，有些机器上的中文部分是英文，但不论是中英文环境， 后面的“<1ms TTL=62”字样总是固定的，它表明一次ping的结果是能通的。如果这个字样出现的次数等于10次即测试的次数，则说明127.0.0.1是百分之百能连通的。
     * <p>
     * 技术上：具体调用dos命令用Runtime.getRuntime().exec实现，查看字符串是否符合格式用正则表达式实现。
     *
     * @param ipAddress
     * @param pingTimes
     * @param timeOut
     * @return
     */
    public static boolean ping(String ipAddress, int pingTimes, int timeOut) {
        BufferedReader in = null;
        Runtime r = Runtime.getRuntime();  // 将要执行的ping命令,此命令是windows格式的命令
        String pingCommand = "ping " + ipAddress + " -n " + pingTimes + " -w " + timeOut;
        try {   // 执行命令并获取输出
            System.out.println(pingCommand);
            Process p = r.exec(pingCommand);
            if (p == null) {
                return false;
            }
            in = new BufferedReader(new InputStreamReader(p.getInputStream()));   // 逐行检查输出,计算类似出现=23ms TTL=62字样的次数
            int connectedCount = 0;
            String line = null;
            while ((line = in.readLine()) != null) {
                connectedCount += getCheckResult(line);
            }   // 如果出现类似=23ms TTL=62这样的字样,出现的次数=测试次数则返回真
            return connectedCount == pingTimes;
        } catch (Exception ex) {
            ex.printStackTrace();   // 出现异常则返回假
            return false;
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //若line含有=18ms TTL=16字样,说明已经ping通,返回1,否則返回0.
    private static int getCheckResult(String line) {  // System.out.println("控制台输出的结果为:"+line);
        Pattern pattern = Pattern.compile("(\\d+ms)(\\s+)(TTL=\\d+)", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(line);
        while (matcher.find()) {
            return 1;
        }
        return 0;
    }

    public static void main(String[] args) throws Exception {
        String ipAddress = "127.0.0.1";
        System.out.println(ping(ipAddress));
        ping02(ipAddress);
        System.out.println(ping(ipAddress, 5, 5000));
    }
}
